/*
	klib_PCIE.c

	contains PCIE control/access functions

	Author: Aidan Goddard 26/1/14
*/

#include"headers/ktypes.h"
#include"headers/klib_PCIE.h"

#include"../kernel/headers/scheduler_functions.h"
#include"../kernel/headers/bakery_lock.h"
#include"../kernel/headers/kernel_tables.h"

// internal lock structures
static SCH_CLS table_lock;
static SCH_CLS bus_lock;

// table pointers
static struct PCIE_DEVICES *devices = (struct PCIE_DEVICES*)(PCI_KOFFSET);
static struct PCIE_DEVICES_RESERVATIONS *res = (struct PCIE_DEVICES_RESERVATIONS*)(PCI_KOFFSET + 0x200000);




uint32_t	__KLIB__PCIEGetDeviceCount(uint32_t *device_count)
{
	// get lock on tables
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&table_lock, CPU);

	// zero counter
	uint32_t i = 0;

	// count the devices
	while(i < 0x10000 && devices[i].handle != 0)
	{
		i++;
	}

	// save the counter
	*device_count = i;

	// unlock tables
	BakeryLeave(&table_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// check device count
	if(i == 0)
	{
		return PCIE_NO_DEVICES;
	}

	// finished
	return PCIE_SUCCESS;
}


uint32_t 	__KLIB__PCIEGetDevices(HANDLE *handles)
{
	// get lock on tables
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&table_lock, CPU);

	// zero counter
	uint32_t i = 0;

	// count the devices
	while(i < 0x10000 && devices[i].handle != 0)
	{
		// get the device handle and increment the counter
		handles[i] = devices[i].handle;
		i++;
	}

	// unlock tables
	BakeryLeave(&table_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// check device count
	if(i == 0)
	{
		return PCIE_NO_DEVICES;
	}

	// finished
	return PCIE_SUCCESS;
}



uint32_t 	__KLIB__PCIEGetDeviceCountOfClass(uint32_t *device_count, uint32_t main_class)
{
	// zero low 16 bits of class code
	main_class = main_class & 0x00ff0000;

	// get lock on tables
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&table_lock, CPU);

	// zero counter & iterator
	uint32_t i = 0;
	uint32_t j = 0;

	// count the devices
	while(i < 0x10000 && devices[i].handle != 0)
	{
		// check class code
		if((devices[i].class_code & 0x00ff0000) == main_class)
		{
			j++;
		}

		i++;
	}

	// save the counter
	*device_count = j;

	// unlock tables
	BakeryLeave(&table_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// check device count
	if(j == 0)
	{
		return PCIE_NO_DEVICES;
	}

	// finished
	return PCIE_SUCCESS;
}


uint32_t 	__KLIB__PCIEGetDevicesOfClass(HANDLE *handles, uint32_t main_class)
{
	// zero low 16 bits of class code
	main_class = main_class & 0x00ff0000;

	// get lock on tables
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&table_lock, CPU);

	// zero counter & iterator
	uint32_t i = 0;
	uint32_t j = 0;

	// count the devices
	while(i < 0x10000 && devices[i].handle != 0)
	{
		// check class code
		if((devices[i].class_code & 0x00ff0000) == main_class)
		{
			handles[j] = devices[i].handle;
			j++;
		}

		i++;
	}

	// unlock tables
	BakeryLeave(&table_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// check device count
	if(j == 0)
	{
		return PCIE_NO_DEVICES;
	}

	// finished
	return PCIE_SUCCESS;
}


uint32_t 	__KLIB__PCIEGetDeviceAddress(HANDLE handle, uint32_t *bus, uint32_t *device, uint32_t *function, uint32_t *IRQ_PIN)
{
	// get lock on tables
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&table_lock, CPU);

	// get index from handle
	uint32_t index = handle & 0xffff;

	// check handle is correct
	if(devices[index].handle != handle)
	{
		// return error
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_INVALID_HANDLE;
	}

	// handle is correct, get details
	*bus = devices[index].bus;
	*device = devices[index].device;
	*function = devices[index].function;
	*IRQ_PIN = devices[index].IRQ_pin;

	// unlock tables
	BakeryLeave(&table_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// finished
	return PCIE_SUCCESS;
}



uint32_t 	__KLIB__PCIEGetDeviceIDs(HANDLE handle, uint32_t *class_code, uint32_t *vendor_ID, uint32_t *device_ID)
{
	// get lock on tables
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&table_lock, CPU);

	// get index from handle
	uint32_t index = handle & 0xffff;

	// check handle is correct
	if(devices[index].handle != handle)
	{
		// return error
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_INVALID_HANDLE;
	}

	// handle is correct, get details
	*class_code = devices[index].class_code;
	*vendor_ID = devices[index].vendor_id;
	*device_ID = devices[index].device_id;

	// unlock tables
	BakeryLeave(&table_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// finished
	return PCIE_SUCCESS;
}


uint32_t	__KLIB__PCIEReserveDevice(HANDLE handle, uint64_t *key)
{
	// get lock on tables
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&table_lock, CPU);

	// get index from handle
	uint32_t index = handle & 0xffff;

	// check handle is correct
	if(devices[index].handle != handle)
	{
		// return error
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_INVALID_HANDLE;
	}

	// check if owned by the system
	if(res[index].reserved_by == 0xffffffff)
	{
		// device is owned by the system
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_DEVICE_OWNED_BY_SYSTEM;
	}

	// handle is correct, check device reservation state
	if(res[index].is_reserved)
	{
		// device is not available
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_DEVICE_NOT_AVAILABLE;
	}

	// otherwise, reserve the device and generate a key
	*key = GetCPUTimeStamp();
	res[index].is_reserved = 1;
	res[index].key = *key;
	res[index].reserved_by = 0;

	// unlock tables
	BakeryLeave(&table_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// finished
	return PCIE_SUCCESS;
}




uint32_t	__KLIB__PCIEFreeDevice(HANDLE handle, uint64_t key)
{
	// get lock on tables
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&table_lock, CPU);

	// get index from handle
	uint32_t index = handle & 0xffff;

	// check handle is correct
	if(devices[index].handle != handle)
	{
		// return error
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_INVALID_HANDLE;
	}

	// handle is correct, check device reservation state
	if(res[index].is_reserved == 0)
	{
		// device is not reserved
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_DEVICE_NOT_RESERVED;
	}

	// check key is correct
	if(res[index].key != key)
	{
		// key is incorrect
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_INVALID_KEY;
	}

	// check if device is owned by the system
	if(res[index].reserved_by == 0xffffffff)
	{
		// device is owned by the system
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_DEVICE_OWNED_BY_SYSTEM;
	}

	// otherwise, mark the device as free
	res[index].is_reserved = 0;
	res[index].key = 0;
	res[index].reserved_by = 0;

	// unlock tables
	BakeryLeave(&table_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// finished
	return PCIE_SUCCESS;
}



uint32_t 	__KLIB__PCIEConfigRead(HANDLE handle, uint64_t key, uint32_t dword_offset, uint32_t *buffer)
{
	// check dword offset value
	if(dword_offset > 63)
	{
		return PCIE_INVALID_DWORD_OFFSET;
	}

	// get lock on tables
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&table_lock, CPU);

	// get index from handle
	uint32_t index = handle & 0xffff;

	// check handle is correct
	if(devices[index].handle != handle)
	{
		// return error
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_INVALID_HANDLE;
	}

	// handle is correct, check device reservation state
	if(res[index].is_reserved == 0)
	{
		// device is not reserved
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_DEVICE_NOT_RESERVED;
	}

	// check key is correct
	if(res[index].key != key)
	{
		// device is owned by the system
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_INVALID_KEY;
	}

	// get the PCIE config area base address for the device
	volatile uint32_t *pcie_base = (uint32_t*)(devices[index].config_base_address);

	// unlock tables
	BakeryLeave(&table_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// lock the bus
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	CPU = GetCPUID();
	BakeryEnter(&bus_lock, CPU);

	// read the value to the buffer
	__asm__ __volatile__("mfence":::"memory");
	*buffer = pcie_base[dword_offset];
	__asm__ __volatile__("mfence":::"memory");

	// unlock the bus
	BakeryLeave(&bus_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");


	// finished
	return PCIE_SUCCESS;
}





uint32_t 	__KLIB__PCIEConfigWrite(HANDLE handle, uint64_t key, uint32_t dword_offset, uint32_t buffer)
{
	// check dword offset value
	if(dword_offset > 63)
	{
		return PCIE_INVALID_DWORD_OFFSET;
	}

	// get lock on tables
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	int CPU = GetCPUID();
	BakeryEnter(&table_lock, CPU);

	// get index from handle
	uint32_t index = handle & 0xffff;

	// check handle is correct
	if(devices[index].handle != handle)
	{
		// return error
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_INVALID_HANDLE;
	}

	// handle is correct, check device reservation state
	if(res[index].is_reserved == 0)
	{
		// device is not reserved
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_DEVICE_NOT_RESERVED;
	}

	// check key is correct
	if(res[index].key != key)
	{
		// device is owned by the system
		BakeryLeave(&table_lock, CPU);
		__asm__ __volatile__("":::"memory");
		__asm__ __volatile__("sti");
		__asm__ __volatile__("":::"memory");
		return PCIE_INVALID_KEY;
	}

	// get the PCIE config area base address for the device
	volatile uint32_t *pcie_base = (uint32_t*)(devices[index].config_base_address);

	// unlock tables
	BakeryLeave(&table_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");

	// lock the bus
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("cli");
	__asm__ __volatile__("":::"memory");
	CPU = GetCPUID();
	BakeryEnter(&bus_lock, CPU);

	// write the value to the config space
	__asm__ __volatile__("mfence":::"memory");
	pcie_base[dword_offset] = buffer;
	__asm__ __volatile__("mfence":::"memory");

	// unlock the bus
	BakeryLeave(&bus_lock, CPU);
	__asm__ __volatile__("":::"memory");
	__asm__ __volatile__("sti");
	__asm__ __volatile__("":::"memory");


	// finished
	return PCIE_SUCCESS;
}






























